package qingclass.qkd.lib.audioplayer

import android.content.Context
import android.os.Handler
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
import java.lang.ref.WeakReference
import java.util.*
import java.util.logging.Level
import java.util.logging.Logger
import qingclass.qkd.lib.audioplayer.Player

/** QkdAudioplayerPlugin */
public class QkdAudioplayerPlugin: FlutterPlugin, MethodCallHandler , ActivityAware{

  companion object{
    private fun buildArguments(playerId: String, value: Any): Map<String, Any>? {
      val result: MutableMap<String, Any> = HashMap()
      result["playerId"] = playerId
      result["value"] = value
      return result
    }
  }

  private val LOGGER = Logger.getLogger(QkdAudioplayerPlugin::class.java.canonicalName)

  private var channel: MethodChannel? = null
  private val mediaPlayers: MutableMap<String?, Player> = HashMap()
  private val handler = Handler()
  private var positionUpdates: Runnable? = null
  private var context: Context? = null
  private var seekFinish = false

  private fun QkdAudioplayerPlugin(channel: MethodChannel, context: Context) {
    this.channel = channel
    this.channel!!.setMethodCallHandler(this)
    this.context = context
    seekFinish = false
  }

  fun QkdAudioplayerPlugin() {
    seekFinish = false
  }

  override fun onMethodCall(call: MethodCall, response: Result) {
    try {
      handleMethodCall(call, response)
    } catch (e: Exception) {
      LOGGER.log(Level.SEVERE, "Unexpected error!", e)
      response.error("Unexpected error!", e.message, e)
    }
  }

  private fun handleMethodCall(call: MethodCall, response: Result) {
    val playerId = call.argument<String>("playerId")
    val mode = call.argument<String>("mode")
    val player = getPlayer(playerId, mode)
    when (call.method) {
      "play" -> {
        val url = call.argument<String>("url")
        val volume = call.argument<Double>("volume")!!
        val position = call.argument<Int>("position")
        val respectSilence = call.argument<Boolean>("respectSilence")!!
        val isLocal = call.argument<Boolean>("isLocal")!!
        val stayAwake = call.argument<Boolean>("stayAwake")!!
        player!!.configAttributes(respectSilence, stayAwake, context!!.applicationContext)
        player.setVolume(volume)
        player.setUrl(url, isLocal)
        if (position != null && mode != "PlayerMode.LOW_LATENCY") {
          player.seek(position)
        }
        player.play()
      }
      "resume" -> {
        player!!.play()
      }
      "pause" -> {
        player!!.pause()
      }
      "stop" -> {
        player!!.stop()
      }
      "release" -> {
        player!!.release()
      }
      "seek" -> {
        val position = call.argument<Int>("position")
        player!!.seek(position!!)
      }
      "setVolume" -> {
        val volume = call.argument<Double>("volume")!!
        player!!.setVolume(volume)
      }
      "setUrl" -> {
        val url = call.argument<String>("url")
        val isLocal = call.argument<Boolean>("isLocal")!!
        player!!.setUrl(url, isLocal)
      }
      "setPlaybackRate" -> {
        val rate = call.argument<Double>("playbackRate")!!
        response.success(player!!.setRate(rate))
        return
      }
      "getDuration" -> {
        response.success(player!!.duration)
        return
      }
      "getCurrentPosition" -> {
        response.success(player!!.currentPosition)
        return
      }
      "setReleaseMode" -> {
        val releaseModeName = call.argument<String>("releaseMode")
        val releaseMode = ReleaseMode.valueOf(releaseModeName!!.substring("ReleaseMode.".length))
        player!!.setReleaseMode(releaseMode)
      }
      else -> {
        response.notImplemented()
        return
      }
    }
    response.success(1)
  }

  private fun getPlayer(playerId: String?, mode: String?): Player? {
    if (!mediaPlayers.containsKey(playerId)) {
      val player = if (mode.equals("PlayerMode.MEDIA_PLAYER", ignoreCase = true)) WrappedMediaPlayer(this, playerId) else WrappedSoundPool(this, playerId)
      mediaPlayers[playerId] = player
    }
    return mediaPlayers[playerId]
  }

  fun handleIsPlaying(player: Player?) {
    startPositionUpdates()
  }

  fun handleDuration(player: Player) {
    channel!!.invokeMethod("audio.onDuration", buildArguments(player.playerId, player.duration))
  }

  fun handleCompletion(player: Player) {
    channel!!.invokeMethod("audio.onComplete", buildArguments(player.playerId, true))
  }

  fun handleSeekComplete(player: Player?) {
    seekFinish = true
  }

  private fun startPositionUpdates() {
    if (positionUpdates != null) {
      return
    }
    positionUpdates = UpdateCallback(mediaPlayers, channel, handler, this)
    handler.post(positionUpdates)
  }

  private fun stopPositionUpdates() {
    positionUpdates = null
    handler.removeCallbacksAndMessages(null)
  }

  override fun onAttachedToEngine(binding: FlutterPluginBinding) {
//        channel = new MethodChannel(binding.getFlutterEngine().getDartExecutor(), "qkd_audioplayer")
    channel = MethodChannel(binding.flutterEngine.dartExecutor, "xyz.luan/audioplayers")
    channel!!.setMethodCallHandler(this)
  }

  override fun onDetachedFromEngine(binding: FlutterPluginBinding) {
    channel!!.setMethodCallHandler(null)
  }

  override fun onAttachedToActivity(activityPluginBinding: ActivityPluginBinding) {
    context = activityPluginBinding.activity
  }

  override fun onDetachedFromActivityForConfigChanges() {}

  override fun onDetachedFromActivity() {
    context = null
  }

  override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
    context = null
  }

  private class UpdateCallback(mediaPlayers: Map<String?, Player>,
                               channel: MethodChannel?,
                               handler: Handler,
                               audioplayersPlugin: QkdAudioplayerPlugin) : Runnable {
    private val mediaPlayers: WeakReference<Map<String?, Player>>
    private val channel: WeakReference<MethodChannel?>
    private val handler: WeakReference<Handler>
    private val audioplayersPlugin: WeakReference<QkdAudioplayerPlugin>
    override fun run() {
      val mediaPlayers = mediaPlayers.get()
      val channel = channel.get()
      val handler = handler.get()
      val audioplayersPlugin = audioplayersPlugin.get()
      if (mediaPlayers == null || channel == null || handler == null || audioplayersPlugin == null) {
        audioplayersPlugin?.stopPositionUpdates()
        return
      }
      var nonePlaying = true
      for (player in mediaPlayers.values) {
        if (!player.isActuallyPlaying) {
          continue
        }
        try {
          nonePlaying = false
          val key = player.playerId
          val duration = player.duration
          val time = player.currentPosition
          channel.invokeMethod("audio.onDuration", buildArguments(key, duration))
          channel.invokeMethod("audio.onCurrentPosition", buildArguments(key, time))
          if (audioplayersPlugin.seekFinish) {
            channel.invokeMethod("audio.onSeekComplete", buildArguments(player.playerId, true))
            audioplayersPlugin.seekFinish = false
          }
        } catch (e: UnsupportedOperationException) {
        }
      }
      if (nonePlaying) {
        audioplayersPlugin.stopPositionUpdates()
      } else {
        handler.postDelayed(this, 200)
      }
    }

    init {
      this.mediaPlayers = WeakReference(mediaPlayers)
      this.channel = WeakReference(channel)
      this.handler = WeakReference(handler)
      this.audioplayersPlugin = WeakReference(audioplayersPlugin)
    }
  }
}
